import { LocationComponent, LockInfo } from "../../../../Model/Server/Module/ActorLocation/LocationComponent";
import { ObjectSystemAttribute, FriendOf, ExpandOf } from "../../../../../Decorator/Decorator";
import { Awake2System } from "../../../../../Core/Module/EventSystem/IAwakeSystem";
import { DestroySystem } from "../../../../../Core/Module/EventSystem/IDestroySystem";
import { CoroutineLock } from "../../../../../Core/Module/CoroutineLock/CoroutineLock";
import { CoroutineLockType } from "../../../../../Core/Module/CoroutineLock/CoroutineLockType";
import { CoroutineLockComponent } from "../../../../../Core/Module/CoroutineLock/CoroutineLockComponent";
import { Log } from "../../../../../Core/Module/Log/Log";
import { TimerComponent } from "../../../../../Core/Module/Timer/TimerComponent";

@FriendOf(LockInfo)
@ObjectSystemAttribute()
export class LockInfoAwake2System extends Awake2System<LockInfo, number, CoroutineLock> {

    Awake(self: LockInfo, lockInstanceId: number, coroutineLock: CoroutineLock): void {
        self.CoroutineLock = coroutineLock;
        self.LockInstanceId = lockInstanceId;
    }

}

@FriendOf(LockInfo)
@ObjectSystemAttribute()
export class LockInfoDestroySystem extends DestroySystem<LockInfo> {

    Destroy(self: LockInfo): void {
        self.CoroutineLock.Dispose();
        self.LockInstanceId = 0;
    }

}

/**
 * 使用 Expand 扩展方法
 * 
 * 必须作出以下申明，才能调用对象的扩展方法
 */
declare module "../../../../Model/Server/Module/ActorLocation/LocationComponent" {

    interface LocationComponent {
        Add(key: number, instanceId: number): Promise<void>;
        Remove(key: number): Promise<void>;
        Lock(key: number, instanceId: number, time: number): Promise<void>;
        UnLock(key: number, oldInstanceId: number, newInstanceId: number): void;
        Get(key: number): Promise<number>;
    }

}

@ExpandOf(LocationComponent)
export class LocationComponentSystem {

    private static async Add(self: LocationComponent, key: number, instanceId: number): Promise<void> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.Location, key);
        self.locations[key] = instanceId;
        Log.Info(`location add key: ${key} instanceId: ${instanceId}`);
        coroutineLock.Dispose();
    }

    private static async Remove(self: LocationComponent, key: number): Promise<void> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.Location, key);
        self.locations.Remove(key);
        Log.Info(`location remove key: ${key}`);
        coroutineLock.Dispose();
    }

    private static async Lock(self: LocationComponent, key: number, instanceId: number, time: number = 0): Promise<void> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.Location, key);

        let lockInfo = self.AddChild<LockInfo, number, CoroutineLock>(LockInfo, instanceId, coroutineLock);
        self.lockInfos.Add(key, lockInfo);

        Log.Info(`location lock key: ${key} instanceId: ${instanceId}`);

        if (time > 0) {
            async function TimeWaitAsync(): Promise<void> {
                let lockInfoInstanceId = lockInfo.InstanceId;
                await TimerComponent.Instance.WaitAsync(time);
                if (lockInfo.InstanceId != lockInfoInstanceId) {
                    return;
                }
                Log.Info(`location timeout unlock key: ${key} instanceId: ${instanceId} newInstanceId: ${instanceId}`);
                self.UnLock(key, instanceId, instanceId);
            }
            TimeWaitAsync().then();
        }
        coroutineLock.Dispose();
    }

    private static UnLock(self: LocationComponent, key: number, oldInstanceId: number, newInstanceId: number): void {
        const lockInfo = self.lockInfos.TryGetValue(key);
        if (!lockInfo) {
            Log.Error(`location unlock not found key: ${key} ${oldInstanceId}`);
            return;
        }

        if (oldInstanceId != lockInfo.LockInstanceId) {
            Log.Error(`location unlock oldInstanceId is different: ${key} ${oldInstanceId}`);
            return;
        }

        Log.Info(`location unlock key: ${key} instanceId: ${oldInstanceId} newInstanceId: ${newInstanceId}`);

        self.locations[key] = newInstanceId;

        self.lockInfos.Remove(key);

        // 解锁
        lockInfo.Dispose();
    }

    private static async Get(self: LocationComponent, key: number): Promise<number> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.Location, key);
        const instanceId = self.locations.TryGetValue(key);
        Log.Info(`location get key: ${key} instanceId: ${instanceId}`);
        coroutineLock.Dispose();
        return instanceId;
    }

}